{ This unit imports and wraps a few functions from the OpenSSL bignum
  library. It does not contain everything that is found in bn.h, only
  a small subset, just enough to do some basic arithmetic operations
  and conversions to/from hex and binary.

  Copyright (C) 2011 Bernd Kreuss <prof7bit@googlemail.com>

  ***

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version with the following modification:

  As a special exception, the copyright holders of this library give you
  permission to link this library with independent modules to produce an
  executable, regardless of the license terms of these independent modules,and
  to copy and distribute the resulting executable under terms of your choice,
  provided that you also meet, for each linked independent module, the terms
  and conditions of the license of that module. An independent module is a
  module which is not derived from or based on this library. If you modify
  this library, you may extend this exception to your version of the library,
  but you are not obligated to do so. If you do not wish to do so, delete this
  exception statement from your version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit openssl_bignum;
{$mode objfpc}{$H+}

interface

const
  {$IFDEF UNIX}
    LIBSSL = 'libssl';
  {$ELSE}
    {$IFDEF WIN32}
      LIBSSL = 'libeay32';
    {$ELSE}
      {$ERROR Please send me a patch to make this compile on other platforms}
    {$ENDIF}
  {$ENDIF}

type
  POpenSSLCtx = Pointer;     // A context allocated through OpenSSL
  POpenSSLBN = ^TOpenSSLBN;  // A bignum allocated through OpenSSL
  TOpenSSLBN = record        // we rarely access this directly
    Data   : Pointer;
    Top    : Integer;
    DMax   : Integer;
    Neg    : Integer;
    Flags  : Integer;
  end;

  { TBigNum represents a big integer. The application is responsible
    for explicitly calling Init on every instance before the first
    assignment and Done after they are no longer needed. Comparison
    operators are overloaded.}
  TBigNum = object
    BN: POpenSSLBN;
    CTX: POpenSSLCtx;
    constructor Init;
    constructor Init(Hex: String); // no prefix, big endian, may have minus sign
    constructor Init(BinBuffer: Pointer; Length: SizeInt); // unsigned big endian
    constructor Init(X: TBigNum); // will make a copy
    destructor Done;
    procedure Assign(Hex: String); inline; // no prefix, big endian, may have minus sign
    procedure Assign(BinBuffer: Pointer; Length: SizeInt); inline; // big endian, always positive
    procedure Assign(X: TBigNum); inline; // will make a copy
    procedure Add(X: TBigNum; Result: TBigNum); inline;
    procedure Sub(X: TBigNum; Result: TBigNum); inline;
    procedure Mul(X: TBigNum; Result: TBigNum); inline;
    procedure DivMod(D, DV, REM: TBigNum); inline;
    function IsNegative: Boolean; inline;
    procedure SetNegative(Neg: Boolean); inline;
    procedure Negate; inline;
    function ToHex: String; inline; // big endian, may have minus sign
    function ToBin: String; inline; // big endian unsigned absolute value
    function ToDWord: DWord; inline;
    procedure DebugPrint;
  end;

  operator = (A, B: TBigNum) : Boolean; inline;
  operator > (A, B: TBigNum) : Boolean; inline;
  operator < (A, B: TBigNum) : Boolean; inline;
  operator >= (A, B: TBigNum) : Boolean; inline;
  operator <= (A, B: TBigNum) : Boolean; inline;

function BN_new: POpenSSLBN; cdecl; external LIBSSL;
procedure BN_clear(BN: POpenSSLBN); cdecl; external LIBSSL;
procedure BN_free(BN: POpenSSLBN); cdecl; external LIBSSL;
function BN_CTX_new: POpenSSLCtx; cdecl; external LIBSSL;
procedure BN_CTX_free(CTX: POpenSSLCtx); cdecl; external LIBSSL;
function BN_copy(A, B: POpenSSLBN): POpenSSLBN cdecl; external LIBSSL;
function BN_num_bits(BN: POpenSSLBN): SizeInt; cdecl; external LIBSSL;
function BN_bin2bn(const Buffer; Size: SizeInt; Ret: POpenSSLBN): POpenSSLBN; cdecl; external LIBSSL;
function BN_hex2bn(var BN: POpenSSLBN; Hex: PChar): Integer; cdecl; external LIBSSL;
function BN_bn2hex(BN: POpenSSLBN): PChar; cdecl external LIBSSL;
function BN_bn2bin(BN: POpenSSLBN; out buf): Integer; cdecl external LIBSSL;
function BN_get_word(BN: POpenSSLBN): DWord; cdecl; external LIBSSL;
function BN_add(R, A, B: POpenSSLBN): Integer; cdecl; external LIBSSL;
function BN_sub(R, A, B: POpenSSLBN): Integer; cdecl; external LIBSSL;
function BN_mul(R, A, B: POpenSSLBN; CTX: POpenSSLCtx): Integer; cdecl; external LIBSSL;
function BN_div(DV, REM, A, D: POpenSSLBN; CTX: POpenSSLCtx): Integer; cdecl; external LIBSSL;
function BN_cmp(A, B: POpenSSLBN): Integer; cdecl; external LIBSSL;
procedure BN_set_negative(A: POpenSSLBN; N: Integer); cdecl; external LIBSSL;
procedure CRYPTO_free(P: Pointer); cdecl; external LIBSSL;

implementation

operator = (A, B: TBigNum): Boolean;
begin
  Result := BN_cmp(A.BN, B.BN) = 0;
end;

operator > (A, B: TBigNum): Boolean;
begin
  Result := BN_cmp(A.BN, B.BN) > 0;
end;

operator < (A, B: TBigNum): Boolean;
begin
  Result := BN_cmp(A.BN, B.BN) < 0;
end;

operator >= (A, B: TBigNum): Boolean;
begin
  Result := BN_cmp(A.BN, B.BN) >= 0;
end;

operator <= (A, B: TBigNum): Boolean;
begin
  Result := BN_cmp(A.BN, B.BN) <= 0;
end;

{ TBigNum }

constructor TBigNum.Init;
begin
  BN := BN_new;
  CTX := BN_CTX_new;
end;

constructor TBigNum.Init(Hex: String);
begin
  init;
  Assign(Hex);
end;

constructor TBigNum.Init(BinBuffer: Pointer; Length: SizeInt);
begin
  Init;
  Assign(BinBuffer, Length);
end;

constructor TBigNum.Init(X: TBigNum);
begin
  Init;
  Assign(X);
end;

destructor TBigNum.Done;
begin
  BN_Clear(BN);
  BN_free(BN);
  BN_CTX_free(CTX);
end;

procedure TBigNum.Assign(Hex: String);
begin
  BN_hex2bn(BN, PChar(Hex));
end;

procedure TBigNum.Assign(BinBuffer: Pointer; Length: SizeInt);
begin
  BN_bin2bn(BinBuffer^, Length, BN);
end;

procedure TBigNum.Assign(X: TBigNum);
begin
  BN_copy(BN, x.BN);
end;

procedure TBigNum.Add(X: TBigNum; Result: TBigNum);
begin
  BN_add(Result.BN, BN, X.BN);
end;

procedure TBigNum.Sub(X: TBigNum; Result: TBigNum);
begin
  BN_sub(Result.BN, BN, X.BN);
end;

procedure TBigNum.Mul(X: TBigNum; Result: TBigNum);
begin
  BN_mul(Result.BN, BN, X.BN, CTX);
end;

procedure TBigNum.DivMod(D, DV, REM: TBigNum);
begin
  BN_div(DV.BN, REM.BN, BN, D.BN, CTX);
end;

function TBigNum.IsNegative: Boolean;
begin
  Result := Boolean(BN^.Neg);
end;

procedure TBigNum.SetNegative(Neg: Boolean);
begin
  BN^.Neg := Integer(Neg);
end;

procedure TBigNum.Negate;
begin
  SetNegative(Not IsNegative);
end;

function TBigNum.ToHex: String;
var
  Ret : PChar;
begin
  Ret := BN_bn2hex(BN);
  Result := AnsiString(Ret);
  CRYPTO_free(Ret);
end;

function TBigNum.ToBin: String;
begin
  SetLength(Result, (BN_num_bits(BN)+7) div 8);
  BN_bn2bin(BN, Result[1]);
end;

function TBigNum.ToDWord: DWord;
begin
  Result := BN_get_word(BN);
end;

procedure TBigNum.DebugPrint;
begin
  writeln(ToHex);
end;

end.

